package com.yhw.srb.core.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.yhw.common.exception.BusinessException;
import com.yhw.srb.core.enums.LendStatusEnum;
import com.yhw.srb.core.enums.ReturnMethodEnum;
import com.yhw.srb.core.enums.TransTypeEnum;
import com.yhw.srb.core.hfb.HfbConst;
import com.yhw.srb.core.hfb.RequestHelper;
import com.yhw.srb.core.mapper.BorrowerMapper;
import com.yhw.srb.core.mapper.UserAccountMapper;
import com.yhw.srb.core.mapper.UserInfoMapper;
import com.yhw.srb.core.pojo.bo.TransFlowBO;
import com.yhw.srb.core.pojo.entity.*;
import com.yhw.srb.core.mapper.LendMapper;
import com.yhw.srb.core.pojo.vo.BorrowInfoApprovalVO;
import com.yhw.srb.core.pojo.vo.BorrowerDetailVO;
import com.yhw.srb.core.service.*;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.yhw.srb.core.util.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * <p>
 * 标的准备表 服务实现类
 * </p>
 *
 * @author yhw
 * @since 2022-08-29
 */
@Service
@Slf4j
public class LendServiceImpl extends ServiceImpl<LendMapper, Lend> implements LendService {

    @Resource
    private DictService dictService;

    @Resource
    private BorrowerMapper borrowerMapper;

    @Resource
    private BorrowerService borrowerService;

    @Resource
    private UserInfoMapper userInfoMapper;

    @Resource
    private UserAccountMapper userAccountMapper;

    @Resource
    private TransFlowService transFlowService;

    @Resource
    private LendItemService lendItemService;

    @Resource
    private LendReturnService lendReturnService;

    @Resource
    private LendItemReturnService lendItemReturnService;

    @Override
    public void createLend(BorrowInfoApprovalVO borrowInfoApprovalVO, BorrowInfo borrowInfo) {
        Lend lend = new Lend();
        lend.setUserId(borrowInfo.getUserId());    // 借款人 id
        lend.setBorrowInfoId(borrowInfo.getId());  // 借款信息 id
        lend.setLendNo(LendNoUtils.getLendNo());  // 利用 编号工具类 生成一个 编号(流水号)
        lend.setTitle(borrowInfoApprovalVO.getTitle());  // 标题
        lend.setAmount(borrowInfo.getAmount());  // 标的金额
        lend.setPeriod(borrowInfo.getPeriod());  // 期数
        lend.setLendYearRate(borrowInfoApprovalVO.getLendYearRate().divide(new BigDecimal(100))); // 年化利率    以前端传过来的为准
        lend.setServiceRate(borrowInfoApprovalVO.getServiceRate().divide(new BigDecimal(100)));  //  平台服务费率 以前端传过来的为准
        lend.setReturnMethod(borrowInfo.getReturnMethod());  // 还款方式
        lend.setLowestAmount(new BigDecimal(100));   // 最低投资金额
        lend.setInvestAmount(new BigDecimal(0));     // 已投金额
        lend.setInvestNum(0);  //投资人数
        lend.setPublishDate(LocalDateTime.now());   // 发布日期

        // 起息日期(开始日期)
        // len中是需要LocalDate 类型  而前端传过来的是 String 类型  所以这里要转换一下
        String lendStartDate = borrowInfoApprovalVO.getLendStartDate();
        DateTimeFormatter ofPattern = DateTimeFormatter.ofPattern("yyyy-MM-dd");
        LocalDate localDate = LocalDate.parse(lendStartDate, ofPattern);  // 第一个参数：要转换的文本  第二个参数：格式
        lend.setLendStartDate(localDate);

        //结束日期
        LocalDate lendEndtDate = localDate.plusMonths(borrowInfo.getPeriod());  //  开始日期 + 投资期数  = 结束日期
        lend.setLendEndDate(lendEndtDate);

        lend.setLendInfo(borrowInfoApprovalVO.getLendInfo());  // 描述 说明

        // 平台预期收益
        // 平台服务费率 是 指1年的
        // 那么 月的 = 平台服务费率 / 12                                                            // ROUND_UP 向上取整(最后一位+1，但最后一位是0就不加)
        // 平台收益 = 标的金额 * 月的 * 期数                                           // 保留8位小数  // 向下取整(不会四舍五入)
        BigDecimal monthRate = lend.getServiceRate().divide(new BigDecimal(12),8,BigDecimal.ROUND_DOWN);  // 月的
        Integer period = lend.getPeriod();  // 期数
        BigDecimal monthMultiply = monthRate.multiply(new BigDecimal(period));  //  月的 * 期数
        BigDecimal expectAmount = lend.getAmount().multiply(monthMultiply);  // 标的金额 * 月的 * 期数
        lend.setExpectAmount(expectAmount);  // 平台收益

        //实际收益
        lend.setRealAmount(new BigDecimal(0));
        //状态
        lend.setStatus(LendStatusEnum.INVEST_RUN.getStatus());   //  INVEST_RUN(1, "募资中"),

        //审核时间
        lend.setCheckTime(LocalDateTime.now());   // 标的审核  其实标的生成后 还需要管理员审核一遍 确认信息的 才把标放出去的  现在我们这里只有一个管理员 就直接给它通过了
        //审核人
        lend.setCheckAdminId(1L);

        baseMapper.insert(lend);
    }

    @Override
    public List<Lend> selectList() {

        List<Lend> lendList = baseMapper.selectList(null);
        for (Lend lend : lendList) {
            String returnMethod = dictService.getNameByParentDictCodeAndValue("returnMethod", lend.getReturnMethod());
            String status = LendStatusEnum.getMsgByStatus(lend.getStatus());
            lend.getParam().put("returnMethod",returnMethod);
            lend.getParam().put("status",status);
        }

        return lendList;
    }

    @Override
    public Map<String, Object> getLendDetail(Long id) {

        // 标的信息
        Lend lend = baseMapper.selectById(id);
        // 数据转换
        String returnMethod = dictService.getNameByParentDictCodeAndValue("returnMethod", lend.getReturnMethod());
        String status = LendStatusEnum.getMsgByStatus(lend.getStatus());
        lend.getParam().put("returnMethod", returnMethod);
        lend.getParam().put("status", status);

        // 借款人信息
        LambdaQueryWrapper<Borrower> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(Borrower::getUserId,lend.getUserId());
        Borrower borrower = borrowerMapper.selectOne(queryWrapper);
        //数据转换
        BorrowerDetailVO borrowerDetailVO = borrowerService.getBorrowerDetailVOById(borrower.getId());

        // 组装数据
        Map<String, Object> map = new HashMap<>();
        map.put("lend",lend);
        map.put("borrower",borrowerDetailVO);

        return map;
    }

    @Override
    public BigDecimal getInterestCount(BigDecimal invest, BigDecimal yearRate, Integer totalmonth, Integer returnMethod) {

        BigDecimal interestCount = new BigDecimal(-1);
        //计算总利息
        if (returnMethod == ReturnMethodEnum.ONE.getMethod()){
            interestCount = Amount1Helper.getInterestCount(invest, yearRate, totalmonth);
        }else if (returnMethod == ReturnMethodEnum.TWO.getMethod()){
            interestCount = Amount2Helper.getInterestCount(invest, yearRate, totalmonth);
        }else if (returnMethod == ReturnMethodEnum.THREE.getMethod()){
            interestCount = Amount3Helper.getInterestCount(invest, yearRate, totalmonth);
        }else if (returnMethod == ReturnMethodEnum.FOUR.getMethod()){
            interestCount = Amount4Helper.getInterestCount(invest, yearRate, totalmonth);
        }

        return interestCount;
    }


    // 放款
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void makeLoan(Long lendId) {

        //获取标的信息
        Lend lend = baseMapper.selectById(lendId);

        //放款接口调用
        Map<String, Object> paramMap = new HashMap<>();
        paramMap.put("agentId", HfbConst.AGENT_ID);
        paramMap.put("agentProjectCode", lend.getLendNo());//标的编号
        String agentBillNo = LendNoUtils.getLoanNo(); //放款编号   流水号
        paramMap.put("agentBillNo", agentBillNo);

        BigDecimal investAmount = lend.getInvestAmount();  // 已投金额
        BigDecimal serviceRate = lend.getServiceRate();    // 平台服务费   这个是按一年来的
        Integer period = lend.getPeriod();    // 标的期数

        // 保留8位小数   // 向下取整（不会四舍五入） BigDecimal.ROUND_UP 向上取整 （最后一位数加1，最后一位数为0就不加）
        BigDecimal monthRate = serviceRate.divide(new BigDecimal(12), 8, BigDecimal.ROUND_DOWN);  // 这是一个月的
        // 平台实际收益 = 已投金额 * 月年化 * 标的期数
        BigDecimal realAmount = investAmount.multiply(monthRate).multiply(new BigDecimal(period));

        paramMap.put("mchFee", realAmount); //商户手续费(平台实际收益)

        paramMap.put("timestamp", RequestHelper.getTimestamp()); // 时间戳

        String sign = RequestHelper.getSign(paramMap);
        paramMap.put("sign",sign);  // 签名

        log.info("放款参数：" + JSONObject.toJSONString(paramMap));
        //发送同步远程调用
        JSONObject result = RequestHelper.sendRequest(paramMap, HfbConst.MAKE_LOAD_URL);
        log.info("放款结果：" + result.toJSONString());

        // 放款失败
        if (!"0000".equals(result.getString("resultCode"))){   // 0000=放款成功
            throw new BusinessException(result.getString("resultMsg"));
        }

        // =========放款成功============
        /*
            1. 更新标的 实际收益  状态  放款时间
            2. 更新用户余额  借款人 投资人
            3. 新增交易流水  借款人 投资人
            4. 放款成功 生成 借款人还款表 和 投资人回款表
         */
        lend.setRealAmount(realAmount);
        lend.setStatus(LendStatusEnum.PAY_RUN.getStatus());   // 从 INVEST_RUN(1, "募资中") 到  PAY_RUN(2, "还款中")
        lend.setPaymentTime(LocalDateTime.now());
        baseMapper.updateById(lend);

        // 借款人
        // 更新 用户余额
        Long userId = lend.getUserId(); // 借款人id
        UserInfo userInfo = userInfoMapper.selectById(userId);  // 借款人信息
        String bindCode = userInfo.getBindCode();  // 借款人的bindCode

        BigDecimal total = new BigDecimal(result.getString("voteAmt"));  // 放款金额
        userAccountMapper.updateAccount(bindCode,total,new BigDecimal(0));  // 更新 借款人 用户余额

        //新增交易流水
        TransFlowBO transFlowBO = new TransFlowBO(
                agentBillNo,
                bindCode,
                total,
                TransTypeEnum.BORROW_BACK,
                "借款放款到账，编号：" + lend.getLendNo());
        transFlowService.saveTransFlow(transFlowBO);    // 新增 借款人 交易流水

        // 投资人
        // 获取投资列表信息   该项目(标的)可能不止一个投资人
        List<LendItem> lendItemList = lendItemService.selectByLendId(lendId, 1);  //  状态（0：默认 1：已支付 2：已还款）
        for (LendItem lendItem : lendItemList) {

            // 更新 用户余额
            Long investUserId = lendItem.getInvestUserId();  // 投资人id
            UserInfo investUserInfo = userInfoMapper.selectById(investUserId);  // 投资人 信息
            String investBindCode = investUserInfo.getBindCode();   // 投资人的bindCode

            BigDecimal investAmountItem = lendItem.getInvestAmount();  // 投资金额   investAmountItem.negate() 取负数
            userAccountMapper.updateAccount(investBindCode,new BigDecimal(0),investAmountItem.negate()); // 更新 投资人 用户余额

            //新增交易流水
            TransFlowBO investTransFlowBO = new TransFlowBO(
                    LendNoUtils.getTransNo(),
                    investBindCode,
                    investAmountItem,
                    TransTypeEnum.BORROW_BACK,
                    "借款放款到账，编号：" + lend.getLendNo());
            transFlowService.saveTransFlow(investTransFlowBO);    // 新增 投资人 交易流水
        }


        // 放款成功 生成 借款人还款表 和 投资人回款表
        this.repaymentPlan(lend);
    }


    /*
     *   借款人 还款表   lend_return
     */
    private void repaymentPlan(Lend lend) {

        List<LendReturn> lendReturnList = new ArrayList<>();

        // 分多少期 多少条记录  每一条记录就是每一期要还的钱
        Integer period = lend.getPeriod();  // 投资期数
        for (int i = 1; i <= period; i++) {

            LendReturn lendReturn = new LendReturn();
            lendReturn.setCurrentPeriod(i);   // 当前的期数
            lendReturn.setUserId(lend.getUserId());  // 借款人id
            lendReturn.setLendId(lend.getId());  // 标的id
            lendReturn.setBorrowInfoId(lend.getBorrowInfoId());  // 借款信息id
            lendReturn.setReturnNo(LendNoUtils.getReturnNo());  // 还款批次号  流水号
            lendReturn.setAmount(lend.getAmount());   // 借款金额  这里是指打算借多少钱
            lendReturn.setBaseAmount(lend.getInvestAmount());  // 计息本金额  这里是指放款多少钱  比如没有达标 也放款了  10W 8W
            lendReturn.setLendYearRate(lend.getLendYearRate()); // 年化利率
            lendReturn.setReturnMethod(lend.getReturnMethod());  // 还款方式

            //说明：还款表中的这三项 = 回款计划中对应的这三项和：因此需要先生成对应的回款计划
            /*
                   如果是先计算 要还款多少  再拿这个数去 让 投资人 按照 投资金额的比例来分  1.麻烦   2.因为精度损失造成钱变少
                   比如： 借款人这期要还 100   有3个投资人  他们投资金额一样 比例一样  每个人得到33.33  那还有0.01？
                   所以 最好还是先计算出 每个人投资人 每期要多少钱 然后他们这一期的相加 就是 借款人 这期要还的
             */
            // lendReturn.setPrincipal();  // 本期要还的本金
            // lendReturn.setInterest();   // 本期要还的利息
            // lendReturn.setTotal();      // 本期要还的本息（本金+利息）

            lendReturn.setFee(new BigDecimal(0));  // 手续费
            LocalDate returnTime =lend.getPaymentTime().plusMonths(i).toLocalDate();
            lendReturn.setReturnDate(returnTime);  // 还款时间  = 放款时间 + 第几期
            lendReturn.setOverdue(false);  // 是否逾期

            /*
                最后一期 和 前面几期 的算法是不一样的
                比如： 一共要还 100     分3期   每一期33.33
                最后得到是 99.99   那还有0.01？
                所以 最后一期 应该是  =  一共要还的  -  前面几期的“和”     100 - （33.33 + 33.33） = 33.34
             */
            if (i == period){  // 最后一期
                lendReturn.setLast(true);
            }else { // 不是最后一期
                lendReturn.setLast(false);
            }

            lendReturn.setStatus(0);  // 状态（0-未归还 1-已归还）

            lendReturnList.add(lendReturn); // 加入总 最后批量保存
        }

        lendReturnService.saveBatch(lendReturnList);  ////批量保存

        //获取lendReturnList中还款期数与还款计划id对应map    比如： 第一期：101  第二期：102  第三期：103
        Map<Integer, Long> lendReturnMap = lendReturnList.stream().collect(
                Collectors.toMap(LendReturn::getCurrentPeriod, LendReturn::getId)
        );

        //======================================================
        //=============获取所有投资者，生成回款表====================
        //=============要通过回款表，计算出还款表的 本金 利息 本息======
        //======================================================

        //当前这个还款的 所有回款表
        List<LendItemReturn> lendItemReturnAllList = new ArrayList<>();

        //获取投资成功的投资记录
        List<LendItem> lendItemList = lendItemService.selectByLendId(lend.getId(), 1);
        for (LendItem lendItem : lendItemList) {
            List<LendItemReturn> lendItemReturns = this.returnInvest(lendItem.getId(), lendReturnMap, lend);
            lendItemReturnAllList.addAll(lendItemReturns);
        }

        //更新 还款表 中的本金 利息 本息
        for (LendReturn lendReturn : lendReturnList) {
                                                           // 流操作  遍历
            BigDecimal sumPrincipal = lendItemReturnAllList.stream()
                    //过滤条件：当回款计划中的还款计划id == 当前还款计划id的时候
                    .filter(item -> item.getLendReturnId().longValue() == lendReturn.getId().longValue())
                    //取属性
                    .map(LendItemReturn::getPrincipal)
                    //做操作
                    .reduce(BigDecimal.ZERO, BigDecimal::add);

                                                           // 流操作  遍历
            BigDecimal sumInterest = lendItemReturnAllList.stream()
                    //过滤条件：当回款计划中的还款计划id == 当前还款计划id的时候
                    .filter(item -> item.getLendReturnId().longValue() == lendReturn.getId().longValue())
                    //取属性
                    .map(LendItemReturn::getInterest)
                    //做操作
                    .reduce(BigDecimal.ZERO, BigDecimal::add);

                                                          // 流操作  遍历
            BigDecimal sumTotal = lendItemReturnAllList.stream()
                    //过滤条件：当回款计划中的还款计划id == 当前还款计划id的时候
                    .filter(item -> item.getLendReturnId().longValue() == lendReturn.getId().longValue())
                    //取属性
                    .map(LendItemReturn::getTotal)
                    //做操作
                    .reduce(BigDecimal.ZERO, BigDecimal::add);

            lendReturn.setPrincipal(sumPrincipal); //每期还款的本金
            lendReturn.setInterest(sumInterest); //每期还款的利息
            lendReturn.setTotal(sumTotal); //每期还款的本息
        }

        lendReturnService.updateBatchById(lendReturnList); //批量更新
    }

    /*
     *  投资人 回款表   lend_item_return
     *
     * @param lendItemId
     * @param lendReturnMap 还款期数与还款计划id对应map
     * @param lend
     * @return
     */
    public List<LendItemReturn> returnInvest(Long lendItemId, Map<Integer, Long> lendReturnMap, Lend lend) {

        LendItem lendItem = lendItemService.getById(lendItemId);  // 投标信息

        BigDecimal investAmount = lendItem.getInvestAmount(); //投资金额

        BigDecimal lendYearRate = lendItem.getLendYearRate();  //年化利率

        Integer totalMonth = lend.getPeriod();   //投资期数

        Integer returnMethod = lend.getReturnMethod(); // 还款方式

        Map<Integer, BigDecimal> mapInterest = null;  //还款期数 -> 利息
        Map<Integer, BigDecimal> mapPrincipal = null;  //还款期数 -> 本金

        //根据还款方式计算本金和利息
        // 期数:金额     比如分3期的 ：  1:100  2:100  3:100
        if (returnMethod.intValue() == ReturnMethodEnum.ONE.getMethod().intValue()){
            mapInterest = Amount1Helper.getPerMonthInterest(investAmount, lendYearRate, totalMonth);
            mapPrincipal = Amount1Helper.getPerMonthPrincipal(investAmount, lendYearRate, totalMonth);
        }else if (returnMethod.intValue() == ReturnMethodEnum.TWO.getMethod().intValue()){
            mapInterest = Amount2Helper.getPerMonthInterest(investAmount, lendYearRate, totalMonth);
            mapPrincipal = Amount2Helper.getPerMonthPrincipal(investAmount, lendYearRate, totalMonth);
        }else if (returnMethod.intValue() == ReturnMethodEnum.THREE.getMethod().intValue()){
            mapInterest = Amount3Helper.getPerMonthInterest(investAmount, lendYearRate, totalMonth);
            mapPrincipal = Amount3Helper.getPerMonthPrincipal(investAmount, lendYearRate, totalMonth);
        }else if(returnMethod.intValue() == ReturnMethodEnum.FOUR.getMethod().intValue()) {
            mapInterest = Amount4Helper.getPerMonthInterest(investAmount, lendYearRate, totalMonth);
            mapPrincipal = Amount4Helper.getPerMonthPrincipal(investAmount, lendYearRate, totalMonth);
        }

        //创建 回款表
        List<LendItemReturn> lendItemReturnList = new ArrayList<>();
        for (Map.Entry<Integer, BigDecimal> entry : mapInterest.entrySet()) {

            Integer currentPeriod = entry.getKey();  // 当前期数
            Long lendReturnId = lendReturnMap.get(currentPeriod);  // 通过 当前期数 拿对应的 还款表对应的id

            LendItemReturn lendItemReturn = new LendItemReturn();
            lendItemReturn.setLendReturnId(lendReturnId);  // 标的还款id
            lendItemReturn.setLendItemId(lendItemId);  // 当前回款对应 的投资表id
            lendItemReturn.setLendId(lend.getId()); // 标的id
            lendItemReturn.setInvestUserId(lendItem.getInvestUserId());  // 投资人id
            lendItemReturn.setInvestAmount(lendItem.getInvestAmount()); // 投资金额
            lendItemReturn.setCurrentPeriod(currentPeriod);  // 当前期数
            lendItemReturn.setLendYearRate(lendYearRate);  // 年化利率
            lendItemReturn.setReturnMethod(returnMethod); // 还款方式

            // 本金 利息 本息
            // 注意 最后一期 的计算不一样
            if (lendItemReturnList.size() > 0 && currentPeriod.intValue() == totalMonth.intValue()){

                //最后一期本金 = 投资的本金(投资金额) - 前几次之和       100 - (33.33 + 33.33) = 33.34
                BigDecimal sumPrincipal = lendItemReturnList.stream()
                        .map(LendItemReturn::getPrincipal)
                        .reduce(BigDecimal.ZERO, BigDecimal::add);

                BigDecimal lastPrincipal = investAmount.subtract(sumPrincipal);
                lendItemReturn.setPrincipal(lastPrincipal);  // 本金

                //==================================================

                //最后一期利息 = 预期收益 - 前几次之和       100 - (33.33 + 33.33) = 33.34
                BigDecimal sumInterest = lendItemReturnList.stream()
                        .map(LendItemReturn::getInterest)
                        .reduce(BigDecimal.ZERO, BigDecimal::add);

                BigDecimal expectAmount = lendItem.getExpectAmount();  // 预期收益
                BigDecimal lastSumInterest = expectAmount.subtract(sumInterest);
                lendItemReturn.setInterest(lastSumInterest);  // 利息

            }else {
                lendItemReturn.setPrincipal(mapPrincipal.get(currentPeriod));  // 本金
                lendItemReturn.setInterest(entry.getValue());  // 利息
            }

            lendItemReturn.setTotal(lendItemReturn.getPrincipal().add(lendItemReturn.getInterest()));   // 本息  本金+利息

            lendItemReturn.setFee(new BigDecimal(0));  // 手续费
            lendItemReturn.setReturnDate(lend.getPaymentTime().plusMonths(currentPeriod).toLocalDate());  // 还款日期
            //是否逾期，默认未逾期
            lendItemReturn.setOverdue(false);
            lendItemReturn.setStatus(0);

            lendItemReturnList.add(lendItemReturn);
        }
        lendItemReturnService.saveBatch(lendItemReturnList); // 批量保存

        return lendItemReturnList;
    }

}
